In multiple inheritance, a class can be derived from more than one base classes. The syntax for multiple inheritance is similar to single inheritance except we list all the base classes in sequence instead of one base class in single inheritance.
class ChildClass(<base_class_1>, <base_class_2>, ...):
.... Child Class code
Lets create few classes and expore the features of Multiple Inheritance.
In [24]:
class Base1:
pass
class Base2:
pass
class MultiDerived(Base1, Base2):
pass
Lets create the class MultiDerived child class using Base1 & Base2.
In [25]:
md = MultiDerived()
print(md)
The above object is a blank object with contains no custom defined attributes. Lets explore it in more details.
In [26]:
class P1():
def __init__(self):
print("P-1 init")
class P2():
def __init__(self):
print("P_2 init")
class C1(P1, P2):
pass
class C2(P2, P1):
pass
c1 = C1()
print("*"*20)
c2 = C2()
In the above example, we have created two child classes C1 and C2 with P1, P2 and P2, P1 as parents respectively. You will note that when we created child c1 of class C1, as C1 do not have any __init__ function, Python searched it on parent classes, and executed & returned after finding its first definition, which it found in P1 class, thus it did not ran the __init__ of P2. Similarly c2 ran the __init__ of P2 class. We will explain the algo which is used to find the required functions on parents later in the chapter.
Lets create a child class TechLead which is derived from Lead and Tech classes.
In [2]:
class Tech(object):
def __init__(self, tech):
self._tech_name = tech
@property
def tech_name(self):
return self._tech_name
@tech_name.setter
def tech_name(self, name):
self._tech_name = name
class Lead(object):
def __init__(self, reportee_count):
self._reportee_count = reportee_count
@property
def reportee(self):
return self._reportee_count
@reportee.setter
def reportee(self, count):
self._reportee_count = count
class TechLead(Tech, Lead):
def __init__(self, tech, count):
Lead.__init__(self, count)
Tech.__init__(self, tech)
vivek = TechLead("Java", 2)
print(vivek)
print(vivek.__dict__)
Lets try the same using super function
In [5]:
class Tech(object):
def __init__(self, tech, **kwds):
print("Inside Tech")
self._tech_name = tech
super().__init__(**kwds)
@property
def tech_name(self):
return self._tech_name
@tech_name.setter
def tech_name(self, name):
self._tech_name = name
class Lead(object):
def __init__(self, count_reportee, **kwds):
print("Inside Lead")
self._reportee_count = count_reportee
super().__init__(**kwds)
@property
def reportee(self):
return self._reportee_count
@reportee.setter
def reportee(self, count):
self._reportee_count = count
class TechLead(Tech, Lead):
def __init__(self, name, **kwds):
print("Inside TechLead")
self._name = name
super().__init__(**kwds)
abhi = TechLead(name="Abhishek Kumar", tech="macOS", count_reportee= 10)
print(abhi)
print(abhi.__dict__)
In [9]:
class Tech(object):
def __init__(self, tech, **kwds):
print("Inside Tech")
self._tech_name = tech
# super().__init__(**kwds)
@property
def tech_name(self):
return self._tech_name
@tech_name.setter
def tech_name(self, name):
self._tech_name = name
class Lead(object):
def __init__(self, count_reportee, **kwds):
print("Inside Lead")
self._reportee_count = count_reportee
super().__init__(**kwds)
@property
def reportee(self):
return self._reportee_count
@reportee.setter
def reportee(self, count):
self._reportee_count = count
class TechLead(Tech, Lead
):
def __init__(self, name, **kwds):
print("Inside TechLead")
self._name = name
super().__init__(**kwds)
abhi = TechLead(name="Abhishek Kumar", count_reportee= 10, tech="macOS")
print(abhi)
print(abhi.__dict__)
Few things of importance you will find are as follows
By adding super().__init__(**kwds) in all the parents __init__ functions, and adding super().__init__(**kwds) in child __init__ function, we can make sure that init of every parent is called automatically.
We can use similar to def __init__(self, count_reportee, **kwds) as __init__ function signature, where count_reportee is the needed parameter for that class.
In [3]:
class Base1:
def test(self):
print("in Base1 -> test")
class Base2:
def test(self):
print("in Base2 -> test")
class MultiDerived(Base1, Base2):
def test2(self):
super().test()
Base2.test(Base2)
class MultiDerived2(Base2, Base1):
def test2(self):
super().test()
Base2.test(Base2)
print("Please check the result of test()")
md = MultiDerived()
md.test2()
md.test()
print("*"*10)
md2 = MultiDerived2()
md2.test2()
md2.test()
In [ ]:
Note in the above example, when we created c1 an object of C1 and as C1 class do not have initializing function __init__ thus Python searched its parent
In [ ]:
class Base:
pass
class Derived1(Base):
pass
class Derived2(Derived1):
pass
In the multiple inheritance scenario, any specified attribute is searched first in the current class. If not found, the search continues into parent classes in depth-first, left-right fashion without searching same class twice
In [32]:
class Base:
def test(self):
print("In Base test")
def test_alone(self):
print("In Base test: test_alone")
class Derived1(Base):
def test(self):
print("In Derived1 test")
super().test()
def test_alone(self, val):
print("In Derived1 test: test_alone ", val)
def test_alone(self):
print("In Base test: test_alone")
class Derived2(Derived1):
def test2(self):
print("in Derived2 test2")
obj = Derived2()
obj.test()
obj.test2()
obj.test_alone()
Base.test(Base)
In [9]:
class Base:
def test(self):
print("In Base test")
def test_base(self):
print("test_base")
def test_alone(self):
print("In Base test: test_alone")
class Derived1(Base):
def test(self):
print("In Derived1 test")
super().test()
self.test_base()
def test_alone(self, val):
print("In Derived1 test: test_alone ", val)
self.test()
obj = Derived1()
obj.test()
obj.test_alone("test")
Base.test(Base)
In [11]:
class Base:
def test(self):
print("In Base test")
def test_base(self):
print("test_base")
def test_alone(self):
print("In Base test: test_alone")
class Derived1(Base):
def test(self):
print("In Derived1 test")
super().test()
Base.test_base(Base)
def test_alone(self, val):
print("In Derived1 test: test_alone ", val)
self.test()
obj = Derived1()
obj.test()
obj.test_alone("test")
Base.test(Base)
In [6]:
class Base:
def test(self):
print("In Base test")
def test_alone(self):
print("In Base test: test_alone")
class Derived1(Base):
def test(self):
print("In Derived1 test")
super().test()
def test_alone(self, val):
print("In Derived1 test: test_alone ", val)
def test_alone(self):
print("In Base test: test_alone")
class Derived2(Derived1):
def test2(self):
print("in Derived2 test2")
obj = Derived2()
obj.test()
obj.test2()
obj.test_alone()
Base.test(Base)
In [3]:
class Base():
def test1(self):
print("In Base test")
class Derived1(Base):
def test(self):
print("In Derived1 test")
class Derived3(Derived1):
pass
d = Derived3()
d.test()
d.test1()
In [27]:
class Base():
def test(self):
print("In Base test")
class Derived1(Base):
def test(self):
print("In Derived1 test", end=", ")
return "Golu"
class Derived3(Derived1):
pass
d = Derived3()
print(d.test())
In [39]:
#### Explicitly calling function
class Base:
def test(self):
print("In Base test")
class Derived1(Base):
def test(self):
print("In Derived1 test")
class Derived2(Derived1):
pass
obj = Derived2
obj.test(obj)
Derived2.test(Derived2)
In [41]:
#### Explicitly calling function
class Base:
def test(self):
print("In Base test")
class Derived1(Base):
def test(self):
print("In Derived1 test")
print(type(self))
class Derived2(Derived1):
pass
obj = Derived2
obj.test(obj)
Derived2.test(Derived2)
In [61]:
#### Explicitly calling function
class Base:
def test(self):
print("In Base test")
class Derived1(Base):
def test(self):
print("In Derived1 test")
print(type(self))
class Derived2(Derived1):
pass
obj = Derived2()
obj.test()
Derived2.test(Derived2)
In [2]:
## TODO : Need to find a way to call init of both the parents
In [ ]:
In [3]:
class CL1(object):
def __init__(self):
# super(CL1, self).__init__()
print ("class 1")
class CL2(object):
def __init__(self):
# super(CL2, self).__init__()
print ("class 2")
class CL3(CL1, CL2):
def __init__(self):
super(CL3, self).__init__()
print ("class 3")
instance = CL3()
CMR is explained by Guido at http://python-history.blogspot.com/2010/06/method-resolution-order.html.
We will try to summerize it here.
In [15]:
class A:
def whereiam(self):
print("I am in A")
class B:
def whoiam(self):
print("I am a method")
class C(A, B):
pass
In [19]:
c = C()
print(dir(c))
c.whoiam()
In [21]:
class A:
def whoiam(self):
print("I am in A")
class B:
def whoiam(self):
print("I am a method")
class C(A, B):
pass
In [22]:
c = C()
print(dir(c))
c.whoiam()
In [26]:
class A:
def whereiam(self):
print("I am in A")
class B(A):
def whereiam(self):
print("I am in B")
class C(A):
def whereiam(self):
print("I am in C")
class D(B, C):
def whereiam(self):
print("I am in D")
In [27]:
d = D()
d.whereiam()
In [28]:
class A:
def whereiam(self):
print("I am in A")
class B(A):
def whereiam(self):
print("I am in B")
class C(A):
def whereiam(self):
print("I am in C")
class D(B, C):
pass
In [29]:
d = D()
d.whereiam()
In [30]:
class A:
def whereiam(self):
print("I am in A")
class B(A):
pass
class C(A):
def whereiam(self):
print("I am in C")
class D(B, C):
pass
In [31]:
d = D()
d.whereiam()
In [47]:
class A:
def whereiam(self):
print("I am in A")
class D:
def whereiam(self):
print("I am in D")
class B(A):
pass
class C(D):
pass
class E(B, C):
pass
In [48]:
e = E()
e.whereiam()
In [ ]:
C3 super-class linearization follow the following rules:
In [35]:
class Type(type):
def __repr__(cls):
return cls.__name__
class O(object, metaclass=Type): pass
class A(O): pass
class B(O): pass
class C(O): pass
class D(O): pass
class E(O): pass
class K1(A, B, C): pass
class K2(D, B, E): pass
class K3(D, A): pass
class Z(K1, K2, K3): pass
In [36]:
Z.mro()
Out[36]:
Problem
class First(object):
def __init__(self):
print("first")
class Second(First):
def __init__(self):
print("second")
class Third(First, Second):
def __init__(self):
print("third")
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-37-a1afb9897f3d> in <module>()
7 print("second")
8
----> 9 class Third(First, Second):
10 def __init__(self):
11 print("third")
TypeError: Cannot create a consistent method resolution
order (MRO) for bases First, Second
In [ ]:
In [2]:
class First(object):
def __init__(self):
print("first")
class Second(First):
def __init__(self):
print("second")
class Third(Second):
def __init__(self):
print("third")
In [4]:
Third.mro()
Out[4]:
In [ ]:
In [5]:
class O(object): pass
class A(O): pass
class B(O): pass
class C(O): pass
class D(O): pass
class E(O): pass
class K1(A, B, C): pass
class K2(D, B, E): pass
class K3(D, A): pass
class Z(K1, K2, K3): pass
In [1]:
print(Z.mro())
In [3]:
class O(object): pass
class A(O): pass
class B(O): pass
class C(O): pass
class D(O): pass
class E(O): pass
class K1(A, B, C): pass
class K2(D, B, E): pass
In [6]:
O.__subclasses__()
Out[6]:
thus, we can create the following code the get the sub-classes name
In [14]:
print([cls.__name__ for cls in O.__subclasses__()])
Lets create a small function based on the above code sample
In [13]:
def get_subclasses(cls):
lst = []
for a in cls.__subclasses__():
lst.append(a)
lst.extend(get_subclasses(a))
return set(lst)
get_subclasses(O)
Out[13]:
In [ ]:
In [ ]:
In [ ]: